package challenges;

import java.util.Iterator;
import java.util.LinkedList;

import game.Challenge;
import gfx.Camera;
import gfx.Glyphs;
import gfx.Shapes;

import javax.microedition.khronos.opengles.GL10;

import util.GameVars;
import util.Logger;
import util.VertexBuffer;
import android.graphics.PointF;

public class Squishy extends Challenge{
	private Sky sky;
	private Wall wall;
	private Player player;
	private StageManager sm;
	
	private PointF oldPlayerLoc;
	private PointF scoreLoc = new PointF(5, 5f);
	
	private long timer;
	
	private float high;
	private float g = -.0033f; // Gravity
	
	private boolean roundReady;
	
	public Squishy(){
		player = new Player();
		sm = new StageManager();
		sky = new Sky();
		wall = new Wall();
		oldPlayerLoc = new PointF();
		reset();
	}
	
	public void draw(GL10 gl){
		Shapes.drawHorizontalGradiantSquare(gl, Camera.GL_WIDTH * .5f, Camera.GL_HEIGHT * .5f, Camera.GL_WIDTH * .5f, Camera.GL_HEIGHT * .5f, .1f, .1f, .1f, 1, .5f, .5f, .5f, 1);

		sky.draw(gl);
		
		sm.drawBGObjects(gl);
		player.draw(gl);
		sm.draw(gl);
		
		wall.draw(gl);
				
		super.draw(gl);
		
		if(isGameOver){
			gl.glColor4f(1, 1, 1, 1);
			Glyphs.drawString(gl, "Score: " + sm.getScore(), scoreLoc, GameVars.TEXT_ALIGNMENT_CENTER);
		}
	}
	
	public void update(long time){
		if(reloading)
			reset();
		super.update(time);
		
//		if(!start)
//			return;
		
		wall.update(time);
		
		sky.update(time);
		
		if(isPaused)
			return;
		
		timer += time;
		
		if(player.isDead())
			isGameOver = true;
		
		if(!isGameOver){
			high = -1;
			oldPlayerLoc.set(player.getLoc().x, player.getLoc().y);
			player.update(time);
			player.update((float) ((-1/((timer + 60000) / 60000f) + 1.5f) * .025f));
			
			if(player.isDead)
				return;
			
			sm.update(time);
			sm.update((float) ((-1/((timer + 60000) / 60000f) + 1.5f) * .025f));
		
			if(sm.checkFloor(player.getOldLeft(), player.getLeft()) && player.isFalling)
				if(sm.getHeight(player.getLeft().x) > high){
					high = sm.getHeight(player.getLeft().x);
				}
			if(sm.checkFloor(player.getOldRight(), player.getRight()) && player.isFalling)
				if(sm.getHeight(player.getRight().x) > high){
					high = sm.getHeight(player.getRight().x);
				}
			if(high > 0){
				player.land(high + player.getHalfWidth());
			}
			if(!player.isGrounded()){
				if(sm.floorCollisionCheck(oldPlayerLoc, player.getLoc(), player.getHalfWidth())){
					player.crash();
//					Logger.log("Crashed");
				}
			}
		}
	}
	
	public boolean handleUp(PointF location) {
		if(super.handleUp(location))
			return false;
		if(isPaused)
			return true;
		player.handleUp(location);
		return true;
	}

	public boolean handleDrag(PointF location) {
		if(super.handleDrag(location))
			return false;
		if(isPaused)
			return true;
		player.handleDrag(location);
		return true;
	}

	public boolean handleTouch(PointF location) {
		if(super.handleTouch(location))
			return false;
		if(isPaused)
			return true;
		if(roundReady){
			player.handleTouch(location);
		}
		return true;
	}
	
	private void reset(){
		timer = 0;
		roundReady = true;
		player.reset();
		sm.reset();
		sky.reset();
	}
	
	class Player{
		private PointF loc, left, right, oldLeft, oldRight;
		private PointF touch;

		private float[] square = new float[12], checkSquare = new float[12];
		private float halfWidth = .2f;
		private float maxJump = .15f, maxDrag = 2;
		private float dragX, dragY = 0;
		
		private float dx, dy;
		
		private boolean isFalling, isDead, isGrounded;
		private boolean dragging, up;
		
		public Player(){
			loc = new PointF();
			left = new PointF();
			right = new PointF();
			oldLeft = new PointF();
			oldRight = new PointF();
			touch = new PointF();
			reset();
		}
		
		public void draw(GL10 gl){
			gl.glPushMatrix();
			
			gl.glTranslatef(loc.x, loc.y, 0);
			
			gl.glDisable(GL10.GL_TEXTURE_2D);
			gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
			gl.glFrontFace(GL10.GL_CCW);
			gl.glVertexPointer(3, GL10.GL_FLOAT, 0, VertexBuffer.getVertexBuffer(square));

			gl.glColor4f(0, 1 * (loc.x / 4f), 0, 1);
			gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, square.length / 3);
			gl.glColor4f(1, 1, 1, 1);
			gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
			gl.glEnable(GL10.GL_TEXTURE_2D);
			gl.glPopMatrix();
		}
		
		public void update(long time){
			oldLeft.set(left.x, left.y);
			oldRight.set(right.x, right.y);
			
			// The end check of 15 should work since I'm loading at least to 40 floor pieces (or 20 gls)
			if(loc.x < 0 || loc.y < -1 || loc.x > 15){
				isDead = true;
				return;
			}
			if(!dragging)
				if(up){
					jump(dragX, dragY);
					resetSquare();
					dragX = dragY = 0;
					up = false;
				}
			if(!isGrounded)
				dy += g;
			if(dy < 0) isFalling = true;
			
			loc.set(loc.x + dx, loc.y + dy);
			
			left.set(loc.x + checkSquare[0], loc.y + checkSquare[1]);
			right.set(loc.x + checkSquare[3], loc.y + checkSquare[4]);
		}
		public void update(float scrollX){ loc.set(loc.x - scrollX, loc.y); }
		
		public void crash(){
			// UnUpdate basically
			loc.set(loc.x - dx, loc.y - dy);
			dx = 0; dy = 0;
			
			left.set(loc.x + checkSquare[0], loc.y + checkSquare[1]);
			right.set(loc.x + checkSquare[3], loc.y + checkSquare[4]);
			
			oldLeft.set(left.x, left.y);
			oldRight.set(right.x, right.y);
		}
		
		public void handleUp(PointF location) {
			if(!isGrounded)
				return;
			up = true;
			return;
		}

		public void handleDrag(PointF location) {
			if(!isGrounded)
				return;
			dragging = true;
			
			
			dragY = ((touch.y - location.y) < maxDrag ? ((touch.y - location.y) / maxDrag) : 1);
			dragX = (Math.abs(touch.x - location.x) < maxDrag ? ((touch.x - location.x) / maxDrag) : Math.signum(touch.x - location.x) * 1);
			// Xes
			square[0] = square[6] = -halfWidth - (Math.abs(touch.y - location.y) < maxDrag ? (Math.signum(touch.y - location.y) < 0 ? .5f : 1) * halfWidth * .75f * ((touch.y - location.y) / maxDrag) : (Math.signum(touch.y - location.y) < 0 ? -.5f : 1) * halfWidth * .75f);
			square[3] = square[9] = halfWidth + (Math.abs(touch.y - location.y) < maxDrag ? (Math.signum(touch.y - location.y) < 0 ? .5f : 1) * halfWidth * .75f * ((touch.y - location.y) / maxDrag) : (Math.signum(touch.y - location.y) < 0 ? -.5f : 1) * halfWidth * .75f);
			// Ys
			square[7] = square[10] = halfWidth - (Math.abs(touch.y - location.y) < maxDrag ? halfWidth * .75f * ((touch.y - location.y) / maxDrag) : Math.signum(touch.y - location.y) * halfWidth * .75f);
			
			square[6] -= ((touch.x - location.x) < maxDrag ? halfWidth * .75f * ((touch.x - location.x) / maxDrag) : halfWidth * .75f);
			square[9] -= ((touch.x - location.x) < maxDrag ? halfWidth * .75f * ((touch.x - location.x) / maxDrag) : halfWidth * .75f);

			dragging = false;
			return;
		}

		public void handleTouch(PointF location) {
			if(!isGrounded)
				return;
			touch.set(location);
//			drag = 0;
			return;
		}

		public PointF getLoc(){ return loc; }
		public PointF getLeft(){ return left; }
		public PointF getRight(){ return right; }
		public PointF getOldLeft(){ return oldLeft; }
		public PointF getOldRight(){ return oldRight; }
		public void land(){ isGrounded = true; dy = 0; dx = 0; isFalling = false; player.setY(1.2f); }
		public void land(float landY){ isGrounded = true; dy = 0; dx = 0; isFalling = false; player.setY(landY); }
		public void jump(float fx, float fy){ /*Logger.log("Jump")*/; dx = fx * maxJump * .25f; dy = fy * maxJump; isGrounded = false; };
		public void setY(float y){ loc.set(loc.x, y); }
		public boolean isDead(){ return isDead; }
		public boolean isGrounded(){ return isGrounded; }
		public float getHalfWidth(){ return halfWidth; }
		
		public void resetSquare(){
			square[7] = square[10] = halfWidth;
			square[0] = square[6] = -halfWidth;
			square[3] = square[9] = halfWidth;
		}
		
		public void reset(){
			square[0] = -halfWidth;
			square[1] = -halfWidth;
			square[3] = halfWidth;
			square[4] = -halfWidth;
			square[6] = -halfWidth;
			square[7] = halfWidth;
			square[9] = halfWidth;
			square[10] = halfWidth;
			
			square[2] = square[5] = square[8] = square[11] = 0;

			checkSquare[0] = -halfWidth;
			checkSquare[1] = -halfWidth;
			checkSquare[3] = halfWidth;
			checkSquare[4] = -halfWidth;
			checkSquare[6] = -halfWidth;
			checkSquare[7] = halfWidth;
			checkSquare[9] = halfWidth;
			checkSquare[10] = halfWidth;
			
			checkSquare[2] = checkSquare[5] = checkSquare[8] = checkSquare[11] = 0;
			
			isGrounded = false;
			up = false;
			isDead = false;
			isFalling = true;
			loc.set(5, 3);
			left.set(loc.x + checkSquare[0], loc.y + checkSquare[1]); 
			right.set(loc.x + checkSquare[3], loc.y + checkSquare[4]);
			oldLeft.set(left.x, left.y);
			oldRight.set(right.x, right.y);
			touch.set(0, 0);
			dx = 0;
			dy = 0;
		}
	}
	
	class StageManager{
		private int score;
		private int tick;
		private int maxJumpDistance;
		private int jumpDistance;
		
		private float oldScroll, scroll;
		private float fadeMod;
		private float maxHeightChange;
		private float jumpChance;
		
		private float width, offset;
		
		private float startFade, endFade;
		
		private float prevHeight, height, nextHeight,lastAddedHeight;

		private LinkedList<Boolean> floorArray;
		private LinkedList<Float> floorHeightArray;
		private LinkedList<Byte> bgObjectArray;
		private Iterator<Float> heightIterator;
		private Iterator<Boolean> floorIterator;
		private Iterator<Byte> bgObjectIterator;
		private byte bgObjectPiece;
		@SuppressWarnings("unused")
		private boolean previousFloorPiece, floorPiece, nextFloorPiece;
		
		private byte drawIterator, updateIterator;
		
		public StageManager(){
			reset();
		}
		
		public void draw(GL10 gl){
			floorIterator = floorArray.iterator();
			heightIterator = floorHeightArray.iterator();

			prevHeight = heightIterator.next();
			height = heightIterator.next();
			nextHeight = heightIterator.next();
			
			previousFloorPiece = floorIterator.next();
			floorPiece = floorIterator.next();
			nextFloorPiece = floorIterator.next();
			

			for(drawIterator = 0; drawIterator < 24; ++drawIterator){
				if(floorPiece)
					Shapes.drawSquare(gl, drawIterator * .5f + .75f - scroll, height * .5f, .25f, height * .5f, 0, 0, 0, 1);
				
				previousFloorPiece = floorPiece;
				floorPiece = nextFloorPiece;
				nextFloorPiece = floorIterator.next();
				
				prevHeight = height;
				height = nextHeight;
				nextHeight = heightIterator.next();
			}
			
			floorIterator = floorArray.iterator();
			heightIterator = floorHeightArray.iterator();

			prevHeight = heightIterator.next();
			height = heightIterator.next();
			nextHeight = heightIterator.next();
			
			previousFloorPiece = floorIterator.next();
			floorPiece = floorIterator.next();
			nextFloorPiece = floorIterator.next();

			
			for(drawIterator = 0; drawIterator < 24; ++drawIterator){
				if(floorPiece){
					width = .20f;
					offset = 0f;
					
					if(prevHeight >= height){
						width += .05f;
						offset -= .05f;
					}
					if(nextHeight >= height){
						width += .05f;
						offset += .05f;
					}
					startFade = (drawIterator * .5f + offset - width - scroll - 1.5f) / 7f;
					endFade = (drawIterator * .5f + width + offset - scroll - 1.5f) / 7f;
					
					Shapes.drawHorizontalGradiantSquare(gl, drawIterator * .5f + .75f - scroll + offset, height * .5f - .05f, width, height * .5f, startFade, startFade, startFade, 1, endFade, endFade, endFade, 1);
				}
				
				previousFloorPiece = floorPiece;
				floorPiece = nextFloorPiece;
				nextFloorPiece = floorIterator.next();
				
				prevHeight = height;
				height = nextHeight;
				nextHeight = heightIterator.next();
			}
		}
		
		public void drawBGObjects(GL10 gl){
			bgObjectIterator = bgObjectArray.iterator();
			heightIterator = floorHeightArray.iterator();
			
			height = heightIterator.next();
			bgObjectPiece = bgObjectIterator.next();

			for(drawIterator = 0; drawIterator < 24; ++drawIterator){
				fadeMod = (drawIterator * .5f - scroll) / 7f;
				// Color / Archive
//				switch(bgObjectPiece){
//				case 0: // Nothing
//					break;
//				case 1: // Tree
//					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .4f, .2f, .25f, .1f * fadeMod, .58f * fadeMod, .1f * fadeMod, 1);
//					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .525f, .2f, .075f, .12f * fadeMod, .7f * fadeMod, .12f * fadeMod, 1);
//					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .275f, .2f, .075f, .12f * fadeMod, .7f * fadeMod, .12f * fadeMod, 1);
//					// Tree Trunk
//					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .075f, .075f, .075f, .5f * fadeMod, .2f * fadeMod, .0f * fadeMod, 1);
//					break;
//				case 2: // Shrub
//					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .15f, .25f, .15f, .1f * fadeMod, .58f * fadeMod, .1f * fadeMod, 1);
//					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .15f, .25f, .1f, .12f * fadeMod, .7f * fadeMod, .12f * fadeMod, 1);
//					break;
//				case 3: // Rock?
//					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .1f, .1f, .1f, .4f * fadeMod, .4f * fadeMod, .5f * fadeMod, 1);
//					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll + .15f, height + .05f, .05f, .1f, .4f * fadeMod, .4f * fadeMod, .5f * fadeMod, 1);
//					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll + .15f, height + .075f, .01f, .025f, .6f * fadeMod, .6f * fadeMod, .65f * fadeMod, 1);
//					break;
//				}
				// Grey Scale Test + Border
				switch(bgObjectPiece){
				case 0: // Nothing
					break;
				case 1: // Tree
					// Trunk Border
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .075f, .075f, .075f, 0, 0, 0, 1);
					// Leaves Border
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .4f, .2f, .25f, 0, 0, 0, 1);
					// Leaves
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .4f, .175f, .225f, .26f * fadeMod, .26f * fadeMod, .26f * fadeMod, 1);
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .525f, .175f, .05f, .313f * fadeMod, .313f * fadeMod, .313f * fadeMod, 1);
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .275f, .175f, .05f, .313f * fadeMod, .313f * fadeMod, .313f * fadeMod, 1);
					// Tree Trunk
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .075f, .05f, .075f, .233f * fadeMod, .233f * fadeMod, .233f * fadeMod, 1);
					break;
				case 2: // Shrub
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .15f, .25f, .15f, 0, 0, 0, 1);
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .15f, .225f, .125f, .26f * fadeMod, .26f * fadeMod, .26f * fadeMod, 1);
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .15f, .225f, .075f, .313f * fadeMod, .313f * fadeMod, .313f * fadeMod, 1);
					break;
				case 3: // Rock?
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .1f, .1f, .1f, 0, 0, 0, 1);
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll + .1f, height + .05f, .05f, .1f, 0, 0, 0, 1);
					
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll, height + .1f, .075f, .075f, .4f * fadeMod, .4f * fadeMod, .4f * fadeMod, 1);
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll + .1f, height + .075f, .025f, .05f, .4f * fadeMod, .4f * fadeMod, .4f * fadeMod, 1);
					Shapes.drawSquare(gl, drawIterator * .5f + .25f - scroll + .1f, height + .075f, .01f, .025f, .6f * fadeMod, .6f * fadeMod, .6f * fadeMod, 1);
					break;
				}
				height = heightIterator.next();
				bgObjectPiece = bgObjectIterator.next();
			}
		}
		
		public void update(long time){
			
		}
		public void update(float scrollX){
			oldScroll = scroll;
			scroll += scrollX;
			while(scroll >= 1.5f){
				oldScroll -= .5f;
				scroll -= .5f;
				floorArray.removeFirst();
				floorHeightArray.removeFirst();
				bgObjectArray.removeFirst();
				++score;
			}
			if(floorArray.size() < 40){
				if(Math.random() < jumpChance){
					for(updateIterator = 0; updateIterator < Math.random() * maxJumpDistance + 1; ++updateIterator){
						floorArray.add(false);
						bgObjectArray.add((byte) 0);
						floorHeightArray.add(0f);
					}
					floorArray.add(true);
					bgObjectArray.add((byte) (Math.random() < .1f ? 1 : Math.random() < .15f ? 2 : Math.random() < .15f ? 3 : 0));
					addRise();
				}
				else{
					floorArray.add(true);
					bgObjectArray.add((byte) (Math.random() < .1f ? 1 : Math.random() < .15f ? 2 : Math.random() < .15f ? 3 : 0));
					addRise();
				}
			}
			++tick;
			jumpChance = (-(1/((tick + 900f)/900f)) + 1) * .5f;
			maxJumpDistance = (int) ((-(1/((tick + 2000)/2000)) + 1) * 5);
			maxHeightChange = (-(1/((tick + 3600f)/3600f)) + 1) * 3;
		}

		public boolean checkFloor(PointF oldL, PointF newL){
			if(oldL.y < floorHeightArray.get((int) ((scroll + newL.x) * 2)) || newL.y > floorHeightArray.get((int) ((scroll + newL.x) * 2)))
				return false;
			return floorArray.get((int) ((scroll + newL.x) * 2f)); 
		}
		
		public boolean floorCollisionCheck(PointF oldLoc, PointF loc, float halfWidth){
			if(oldLoc.x  + oldScroll > loc.x + scroll){ // Going left
				// Checking based off a maximum tolerance for casting errors
				if(Math.abs((oldLoc.x + oldScroll - halfWidth) * 2 - (loc.x + scroll - halfWidth) * 2) < .00005f)
					return false;
				if(floorArray.get((int) ((scroll + loc.x - halfWidth) * 2) )){
					if(oldLoc.y - halfWidth < floorHeightArray.get((int) ((scroll + loc.x - halfWidth) * 2f)) && loc.y - halfWidth < floorHeightArray.get((int) ((scroll + loc.x - halfWidth) * 2f)))
						return true;
				}
			}
			else{
				if(Math.abs((oldLoc.x + oldScroll - halfWidth) * 2 - (loc.x + scroll - halfWidth) * 2) < .00005f)
					return false;
				if(floorArray.get((int) ((scroll + loc.x + halfWidth) * 2))){
					if(oldLoc.y - halfWidth < floorHeightArray.get((int) ((scroll + loc.x + halfWidth) * 2f)) && loc.y - halfWidth < floorHeightArray.get((int) ((scroll + loc.x + halfWidth) * 2f)))
						return true;
				}
			}
			return false;
		}
		
		public void addRise(){
			// Chance to rise
			if(Math.random() < 1f - (lastAddedHeight - 1f) / 3f){
				lastAddedHeight = (float) (lastAddedHeight + Math.random() * maxHeightChange);
				if(lastAddedHeight > 4)
					lastAddedHeight = 4;
				floorHeightArray.add(lastAddedHeight);
			}
			else{
				lastAddedHeight = (float) (lastAddedHeight - Math.random() * maxHeightChange);
				if(lastAddedHeight < 1)
					lastAddedHeight = 1;
				floorHeightArray.add(lastAddedHeight);
			}
		}
		
		public int getScore(){ return score; }
		public float getHeight(float x){ return floorHeightArray.get((int) ((scroll + x) * 2f)); }
		
		public void reset(){
			tick = 0;
			maxHeightChange = 0;
			maxJumpDistance = 0;
			jumpChance = .1f;
			score = 0;
			scroll = 1;
			floorHeightArray = new LinkedList<Float>();
			floorArray = new LinkedList<Boolean>();
			bgObjectArray = new LinkedList<Byte>();
			for(int i = 0; i < 15; ++i){
				floorArray.add(true);
				bgObjectArray.add((byte) (Math.random() < .1f ? 1 : Math.random() < .15f ? 2 : Math.random() < .15f ? 3 : 0));
				floorHeightArray.add(1f);
				lastAddedHeight = 1f;
			}
			while(floorArray.size() < 40)
				if(Math.random() < jumpChance){
					jumpDistance = (int) (Math.random() * maxJumpDistance + 1);
					for(updateIterator = 0; updateIterator < jumpDistance; ++updateIterator){
						floorArray.add(false);
						bgObjectArray.add((byte) 0);
						floorHeightArray.add(0f);
					}
					floorArray.add(true);
					bgObjectArray.add((byte) (Math.random() < .1f ? 1 : Math.random() < .15f ? 2 : Math.random() < .15f ? 3 : 0));
					addRise();
				}
				else{
					floorArray.add(true);
					bgObjectArray.add((byte) (Math.random() < .1f ? 1 : Math.random() < .15f ? 2 : Math.random() < .15f ? 3 : 0));
					addRise();
				}
		}
	}
	
	class Wall{
		private PointF[] stationaryLocs, particleLocs;
		
		private byte updateIterator, drawIterator;
		
		private short[] stationaryTick;
		
		private float[][] stationary, particles;
		
		private float[] stationaryStarts,stationaryFrequency, stationaryScale;

		private short[] particleFadeTime, particleCurrentTime;
		private float[] particleStarts, particleGravity, particleDX, particleDY;
		private boolean[] particleReady;

		private float maxDXStart = .025f;
		private float minDXStart = .0025f;
		private float maxDYStart = .025f;
		private float minDYStart = .01f;
		private float minParticleGravity = -.0001f;
		private float maxParticleGravity = -.00033f;
		private float maxParticleSize = .5f;
		private float minParticleSize = .25f;
		private float maxParticleTime = 250;
		private float minParticleTime = 60;
		
		private float maxStationarySize = 1.5f;
		private float minStationarySize = .75f;
		private float minStationaryScale = -.25f;
		private float maxStationaryScale = .5f;
		private float maxStationaryFrequency = 450;
		private float minStationaryFrequency = 120;
		
		public Wall(){
			stationary = new float[16][12];
			particles = new float[16][12];

			stationaryLocs = new PointF[stationary.length];
			particleLocs = new PointF[particles.length];

			stationaryStarts = new float[stationary.length];
			stationaryScale = new float[stationary.length];
			stationaryFrequency = new float[stationary.length];
			stationaryTick = new short[stationary.length];

			particleStarts = new float[particles.length];
			particleFadeTime = new short[particles.length];
			particleCurrentTime = new short[particles.length];
			particleGravity = new float[particles.length];
			particleDX = new float[particles.length];
			particleDY = new float[particles.length];
			particleReady = new boolean[particles.length];
			
			for(int i = 0; i < stationary.length; ++i){
				stationaryStarts[i] = (float) (Math.random() * (maxStationarySize - minStationarySize) + minStationarySize);
				stationaryScale[i] = (float) (Math.random() * (maxStationaryScale - minStationaryScale) + minStationaryScale);
				stationaryFrequency[i] = (float) (Math.random() * (maxStationaryFrequency - minStationaryFrequency) + minStationaryFrequency);
				stationaryTick[i] = (short) (Math.random() * stationaryFrequency[i]);
				
				stationary[i][0] = -stationaryStarts[i] * .5f;
				stationary[i][1] = -stationaryStarts[i] * .5f;
				stationary[i][3] = stationaryStarts[i] * .5f;
				stationary[i][4] = -stationaryStarts[i] * .5f;
				stationary[i][6] = -stationaryStarts[i] * .5f;
				stationary[i][7] = stationaryStarts[i] * .5f;
				stationary[i][9] = stationaryStarts[i] * .5f;
				stationary[i][10] = stationaryStarts[i] * .5f;
				
				stationary[i][2] = stationary[i][5] = stationary[i][8] = stationary[i][11] = 0; 
				
				stationaryLocs[i] = new PointF((float) (Math.random() * .25f), (float) (Math.random() - .5f + i * ((float) (Camera.GL_HEIGHT + 1) / (float) stationary.length)));
			}
			
			for(int i = 0; i < stationary.length; ++i){
				particleStarts[i] = (float) (Math.random() * (maxParticleSize - minParticleSize) + minParticleSize);
				particleFadeTime[i] = (short) (Math.random() * (maxParticleTime - minParticleTime) + minParticleTime);
				particleCurrentTime[i] = (short) (Math.random() * particleFadeTime[i]);
				particleGravity[i] = (float) (Math.random() * (maxParticleGravity - minParticleGravity) + minParticleGravity);
				particleDX[i] = (float) (Math.random() * (maxDXStart - minDXStart) + minDXStart);
				particleDY[i] = (float) (Math.random() * (maxDYStart - minDYStart) + minDYStart);
				particleReady[i] = false;
				
				particles[i][0] = -particleStarts[i] * .5f;
				particles[i][1] = -particleStarts[i] * .5f;
				particles[i][3] = particleStarts[i] * .5f;
				particles[i][4] = -particleStarts[i] * .5f;
				particles[i][6] = -particleStarts[i] * .5f;
				particles[i][7] = particleStarts[i] * .5f;
				particles[i][9] = particleStarts[i] * .5f;
				particles[i][10] = particleStarts[i] * .5f;
				
				particles[i][2] = particles[i][5] = particles[i][8] = particles[i][11] = 0;
				
				particleLocs[i] = new PointF((float) .5f, (float) (Math.random() * (float) Camera.GL_HEIGHT));
			}
		}
		
		public void draw(GL10 gl){
			for(drawIterator = 0; drawIterator < stationary.length; ++drawIterator){
				gl.glPushMatrix();
				
				gl.glDisable(GL10.GL_TEXTURE_2D);
				gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
				gl.glFrontFace(GL10.GL_CCW);
				
				gl.glColor4f(0, 0, 0, 1);

				gl.glPushMatrix();
				gl.glTranslatef(stationaryLocs[drawIterator].x, stationaryLocs[drawIterator].y, 0);
				gl.glScalef((float) (1 + stationaryScale[drawIterator] * Math.cos(stationaryTick[drawIterator] / stationaryFrequency[drawIterator] * 2 * Math.PI)), (float) (1 + stationaryScale[drawIterator] * Math.cos(stationaryTick[drawIterator] / stationaryFrequency[drawIterator] * 2 * Math.PI)), 0);
				
				gl.glVertexPointer(3, GL10.GL_FLOAT, 0, VertexBuffer.getVertexBuffer(stationary[drawIterator]));
				gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, stationary[drawIterator].length / 3);
				gl.glPopMatrix();
				
				gl.glPushMatrix();
				gl.glTranslatef(particleLocs[drawIterator].x, particleLocs[drawIterator].y, 0);
				gl.glScalef(1 - ((float) particleCurrentTime[drawIterator] / (float) particleFadeTime[drawIterator]), 1 - ((float) particleCurrentTime[drawIterator] / (float) particleFadeTime[drawIterator]), 0);
				
				gl.glVertexPointer(3, GL10.GL_FLOAT, 0, VertexBuffer.getVertexBuffer(particles[drawIterator]));
				gl.glColor4f(0, 0, 0, 1);
				gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, particles[drawIterator].length / 3);
				gl.glPopMatrix();
				
				gl.glColor4f(1, 1, 1, 1);
				gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
				gl.glEnable(GL10.GL_TEXTURE_2D);
				gl.glPopMatrix();
			}
		}
		
		public void update(long time){
			for(updateIterator = 0; updateIterator < stationary.length; ++updateIterator){
				stationaryTick[updateIterator] = (short) (stationaryTick[updateIterator] == stationaryFrequency[updateIterator] ? 0 : stationaryTick[updateIterator] + 1);
				
				if(!particleReady[updateIterator]){
					if(particleCurrentTime[updateIterator] >= particleFadeTime[updateIterator])
						particleReady[updateIterator] = true;
					else 
						++particleCurrentTime[updateIterator];
				}
				else {
					if(particleCurrentTime[updateIterator] >= particleFadeTime[updateIterator]){
						particleDX[updateIterator] = (float) (Math.random() * (maxDXStart - minDXStart) + minDXStart);
						particleDY[updateIterator] = (float) (Math.random() * (maxDYStart - minDYStart) + minDYStart);
						particleLocs[updateIterator].set((float) -.25f, (float) (Math.random() * (float) Camera.GL_HEIGHT));
						particleCurrentTime[updateIterator] = 0;
					}
					else{
						particleLocs[updateIterator].set(particleLocs[updateIterator].x + particleDX[updateIterator], particleLocs[updateIterator].y + particleDY[updateIterator]);
						particleDY[updateIterator] += particleGravity[updateIterator];
						++particleCurrentTime[updateIterator];
					}
				}
			}
		}
	}	
	
	
	class Sky{
		private byte updateIterator, drawIterator, resetIterator;
		
		private Cloud[] clouds;
		
		public Sky(){
			clouds = new Cloud[10];

			for(int i = 0; i < clouds.length; ++i)
				clouds[i] = new Cloud();
		}
		
		public void draw(GL10 gl){
			for(drawIterator = 0; drawIterator < clouds.length; ++drawIterator){
				clouds[drawIterator].draw(gl);
			}
		}
		
		public void update(long time){
			for(updateIterator = 0; updateIterator < clouds.length; ++updateIterator)
				clouds[updateIterator].update();
		}
		
		public void reset(){
			for(resetIterator = 0; resetIterator < clouds.length; ++resetIterator)
				clouds[resetIterator].reset();
		}
		
		class Cloud{
			private PointF loc;
			private PointF[] fluffLocs;
			private short fluffIterator, drawIterator;
			private byte fluffCount;
			private byte fluffSide; // 0 = left, 1 = top, 2 = right, 3 = bottom
			private float dx;
			private float maxFluffSize = 1.1f;
			private float minFluffSize = .2f;
			private float[][] fluff;
			private float[] fluffHeights, fluffWidths;
			
			public Cloud(){
				loc = new PointF();
				fluff = new float[5][12];

				fluffWidths = new float[fluff.length];
				fluffHeights = new float[fluff.length];
				
				fluffLocs = new PointF[fluff.length];
				for(int i = 0; i < fluffLocs.length; ++i)
					fluffLocs[i] = new PointF();
				reset();
			}
			
			public void draw(GL10 gl){
				gl.glPushMatrix();
				gl.glTranslatef(loc.x, loc.y, 0);
				for(drawIterator = 0; drawIterator < fluffCount; ++drawIterator){
					Shapes.drawSquare(gl, fluffLocs[drawIterator].x, fluffLocs[drawIterator].y, fluffWidths[drawIterator] * .5f, fluffHeights[drawIterator] * .5f, (loc.x - 1) / 6f, (loc.x - 1) / 6f, (loc.x - 1) / 6f, 1);
				}
				gl.glPopMatrix();
			}
			
			public void update(){
				if(loc.x < -2)
					respawn();
				else loc.set(loc.x - dx, loc.y);
			}
			
			public void setFluff(){
				fluffCount = (byte) (Math.random() * fluff.length);
				
				fluffHeights[0] = (float) (Math.random() * (maxFluffSize - minFluffSize) + minFluffSize);
				fluffWidths[0] = (float) (Math.random() * (maxFluffSize - fluffHeights[0]) + fluffHeights[0]);
				
				// Fluff Zero
				fluffLocs[0].set(0, 0);
				
				for(fluffIterator = 1; fluffIterator < fluff.length; ++fluffIterator){
					fluffHeights[fluffIterator] = (float) (Math.random() * (maxFluffSize - minFluffSize) + minFluffSize);
					fluffWidths[fluffIterator] = (float) (Math.random() * (maxFluffSize - fluffHeights[fluffIterator]) + fluffHeights[fluffIterator]);
					
					fluffSide = (byte) (Math.random() * 4); // 0 = left, 1 = top, 2 = right, 3 = bottom
					switch(fluffSide){
					case 0:
						fluffLocs[fluffIterator].set(- fluffWidths[0] * .5f, (float) (fluffHeights[0] * .5f - (Math.random() * fluffHeights[0])));
						break;
					case 1:
						fluffLocs[fluffIterator].set((float) (fluffWidths[0] * .5f - (Math.random() * fluffWidths[0])), fluffHeights[0] * .5f);
						break;
					case 2:
						fluffLocs[fluffIterator].set(fluffWidths[0] * .5f, (float) (fluffHeights[0] * .5f - (Math.random() * fluffHeights[0])));
						break;
					default:
						fluffLocs[fluffIterator].set((float) (fluffWidths[0] * .5f - (Math.random() * fluffWidths[0])), - fluffHeights[0] * .5f);
						break;
					}
				}
			}
			
			public void respawn(){
				loc.set((float) (Math.random() * (2 + timer / 5000f)) + 12.2f, (float) (Math.random() * 3.3f) + 1.5f); 
				dx = (float) (Math.random() * .01 + .005f);
				setFluff();
			}
			
			public void reset(){
				loc.set((float) (Math.random() * 15), (float) (Math.random() * 3.3f) + 1.5f); 
				dx = (float) (Math.random() * .01 + .005f);
				setFluff();
			}
		}
	}
}